#include "sfc.h"
#include "fh_mc_dma.h"
#include "linux/mtd/spi-nor.h"

enum fh_sfc_iftype {
	IF_TYPE_STD,
	IF_TYPE_DUAL,
	IF_TYPE_QUAD,
};

SINT32 SFC_Enable(UINT32 base, spi_enable_e enable)
{
	SET_REG(REG_SFC_SSIENR(base), enable);

	return 0;
}

void SFC_EnableDma(UINT32 base)
{
	UINT32 data;

	data = GET_REG(REG_SFC_NFC_CMD_MODE(base));
	data |= DMA_MODE;
	SET_REG(REG_SFC_NFC_CMD_MODE(base), data);
}

void SFC_DisableDma(UINT32 base)
{
	UINT32 data;

	data = GET_REG(REG_SFC_NFC_CMD_MODE(base));
	data &= ~DMA_MODE;
	SET_REG(REG_SFC_NFC_CMD_MODE(base), data);
}

SINT32 SFC_SetPolarity(UINT32 base, spi_polarity_e polarity)
{
	UINT32 data;

	data = GET_REG(REG_SFC_CTRL0(base));
	data &= ~(UINT32)SPI_POLARITY_RANGE;
	data |= polarity;
	SET_REG(REG_SFC_CTRL0(base), data);

	return 0;
}

SINT32 SFC_SetPhase(UINT32 base, spi_phase_e phase)
{
	UINT32 data;

	data = GET_REG(REG_SFC_CTRL0(base));
	data &= ~(UINT32)SPI_PHASE_RANGE;
	data |= phase;
	SET_REG(REG_SFC_CTRL0(base), data);

	return 0;
}

SINT32 SFC_SetFrameFormat(UINT32 base, spi_format_e format)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_CTRL0(base));
	data &= ~(UINT32)SPI_FRAME_FORMAT_RANGE;
	data |= format;
	SET_REG(REG_SFC_CTRL0(base), data);

	return 0;
}

SINT32 SFC_SetDataSize(UINT32 base, spi_data_size_e size)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_CTRL0(base));
	data &= ~(UINT32)SPI_DATA_SIZE_RANGE;
	data |= size;
	SET_REG(REG_SFC_CTRL0(base), data);

	return 0;
}

void SFC_SetDataLength(UINT32 base, UINT32 num)
{
	UINT32 val;
	UINT32 mask;

	if(!num)
		return;

	num -= 1;
	mask = (1 << NFC_DATA_LENGTH_SIZE) - 1;
	val = GET_REG(REG_SFC_NFC_CMD_SET0(base));
	val &= ~(mask << NFC_DATA_LENGTH_OFFSET);
	val |= (num & mask) << NFC_DATA_LENGTH_OFFSET;
	SET_REG(REG_SFC_NFC_CMD_SET0(base), val);
}

SINT32 SFC_GetDataLength(UINT32 base)
{
	UINT32 val;

	val = GET_REG(REG_SFC_NFC_CMD_SET0(base));
	val = (val >> NFC_DATA_LENGTH_OFFSET) & 0xfff;
	return val;
}

SINT32 SFC_SetTransferMode(UINT32 base, spi_transfer_mode_e mode)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_CTRL0(base));
	data &= ~(UINT32)SPI_TRANSFER_MODE_RANGE;
	data |= mode;
	SET_REG(REG_SFC_CTRL0(base), data);

	return 0;
}

void SFC_SetCmdType(UINT32 base, u8 optype)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_NFC_CMD_MODE(base));
	data &= ~(NFC_READ_CMD | NFC_WRITE_CMD);
	data |= optype;
	SET_REG(REG_SFC_NFC_CMD_MODE(base), data);
}

SINT32 SFC_SetDataType(UINT32 base, spi_data_type_e type)
{
	UINT32 data = 0;
	UINT32 mask = (1 << DATA_TYPE_SIZE) - 1;

	data = GET_REG(REG_SFC_NFC_CMD_MODE(base));
	data &= ~(mask << DATA_TYPE_OFFSET);
	data |= (type & mask) << DATA_TYPE_OFFSET;
	SET_REG(REG_SFC_NFC_CMD_MODE(base), data);

	return 0;
}

void SFC_Access_Start(UINT32 base)
{
	SET_REG(REG_SFC_NFC_ACCESS_START(base), (0x1 << 0));
}

void SFC_Wait_OP_Finish(UINT32 base)
{
	UINT32 status;

	while (1)
	{
		status = GET_REG(REG_SFC_ISR(base));
		if (status == 0x20)
		{
			status = GET_REG(REG_SFC_INTCLR(base));
			break;
		}
	}
}

SINT32 SFC_SetBaudrate(UINT32 base, spi_baudrate_e baudrate)
{
	SET_REG(REG_SFC_BAUD(base), baudrate);

	return 0;
}

SINT32 SFC_SetSampleDelay(UINT32 base, UINT32 delay)
{
	SET_REG(REG_SFC_SAMPLE_DELAY(base), delay);

	return 0;
}

SINT32 SFC_DisableIrq(UINT32 base, UINT32 irq)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_IMR(base));
	data &= ~irq;
	SET_REG(REG_SFC_IMR(base), data);

	return 0;
}

SINT32 SFC_EnableIrq(UINT32 base, UINT32 irq)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_IMR(base));
	data |= irq;
	SET_REG(REG_SFC_IMR(base), data);

	return 0;
}

SINT32 SFC_ReadStatus(UINT32 base)
{
	return GET_REG(REG_SFC_STATUS(base));
}

SINT32 SFC_EnableSlaveen(UINT32 base, int bus, int cs)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_SER(base));
	data |= 0x1 << 0;
	SET_REG(REG_SFC_SER(base), data);

	return 0;
}

SINT32 SFC_DisableSlaveen(UINT32 base, int bus, int cs)
{
	UINT32 data = 0;

	data = GET_REG(REG_SFC_SER(base));
	data &= ~(0x1 << 0);
	SET_REG(REG_SFC_SER(base), data);

	return 0;
}

void SFC_SetTiming(UINT32 base, UINT32 value)
{
	SET_REG(REG_SFC_TIMCR(base), value);
}

void SFC_SetAddressLength(UINT32 base, UINT32 len)
{
	UINT32 data, mask;

	mask = (1 << NFC_ADDR_LENGTH_SIZE) - 1;
	data = GET_REG(REG_SFC_NFC_CMD_SET0(base));
	data &= ~(mask << NFC_ADDR_LENGTH_OFFSET);
	data |= (len & mask) << NFC_ADDR_LENGTH_OFFSET;
	SET_REG(REG_SFC_NFC_CMD_SET0(base), data);
}

void SFC_SetAddress(UINT32 base, UINT32 addr, UINT32 len)
{
	UINT32 val, data;

	if (!len)
		return;

	data = GET_REG(REG_SFC_NFC_CMD_SET1(base));
	data &= 0xff;
	val  = GET_REG(REG_SFC_NFC_CMD_SET2(base));
	val &= 0xffff0000;

	if (len == 4)
	{
		data |= (ADDR1(addr >> 24) & ADDR1_MASK);
		data |= (ADDR2(addr >> 16) & ADDR2_MASK);
		data |= (ADDR3(addr >> 8) & ADDR3_MASK);
		val = ADDR4(addr) & ADDR4_MASK;
	}
	else
	{
		data |= (ADDR1(addr >> 16) & ADDR1_MASK);
		data |= (ADDR2(addr >> 8) & ADDR2_MASK);
		data |= (ADDR3(addr) & ADDR3_MASK);
	}

	SET_REG(REG_SFC_NFC_CMD_SET1(base), data);
	SET_REG(REG_SFC_NFC_CMD_SET2(base), val);
}

void SFC_Clear_Configure(UINT32 base)
{
	SET_REG(REG_SFC_NFC_CMD_MODE(base), 0);
	SET_REG(REG_SFC_NFC_CMD_SET0(base), 0);
	SET_REG(REG_SFC_NFC_CMD_SET1(base), 0);
	SET_REG(REG_SFC_NFC_CMD_SET2(base), 0);
}

void SFC_Send_CMD(UINT32 base, UINT8 cmd)
{
	UINT32 data;
	UINT32 mask;

	mask = (1 << NFC_CMD_VALUE_SIZE) - 1;
	data = GET_REG(REG_SFC_NFC_CMD_SET1(base));
	data &= ~(mask << NFC_CMD_VALUE_OFFSET);
	data |= (cmd & mask) << NFC_CMD_VALUE_OFFSET;
	SET_REG(REG_SFC_NFC_CMD_SET1(base), data);
}

void SFC_SetXferMode(UINT32 base, UINT8 mode)
{
	UINT32 val, mask;

	mask = (1 << SPI_XF_MODE_SIZE) - 1;
	val = GET_REG(REG_SFC_NFC_CMD_MODE(base));
	val &= ~(mask << SPI_XF_MODE_OFFSET);
	val |= (mode & mask) << SPI_XF_MODE_OFFSET;
	SET_REG(REG_SFC_NFC_CMD_MODE(base), val);
}

void sfc_cs_activate(struct spi_slave *slave)
{
	sfc_controller *sfc = to_fh_sfc(slave);
	SFC_EnableSlaveen(sfc->base, sfc->slave.bus, sfc->slave.cs);
}

void sfc_cs_deactivate(struct spi_slave *slave)
{
	sfc_controller *sfc = to_fh_sfc(slave);
	SFC_DisableSlaveen(sfc->base, sfc->slave.bus, sfc->slave.cs);
}

void sfc_free_slave(struct spi_slave *slave)
{
	sfc_controller *sfc = to_fh_sfc(slave);

	free(sfc);
}

int sfc_claim_bus(struct spi_slave *slave)
{
	sfc_controller *sfc = to_fh_sfc(slave);

	if (!sfc)
		return -1;
	SFC_Enable(sfc->base, SPI_DISABLE);
	SFC_SetSampleDelay(sfc->base, sfc->sample_delay);
	/* frame format */
	SFC_SetFrameFormat(sfc->base, SPI_MOTOROLA_MODE);
	/* spi polarty */
	SFC_SetPolarity(sfc->base, SPI_POLARITY_HIGH);
	/* spi phase */
	SFC_SetPhase(sfc->base, SPI_PHASE_TX_FIRST);
	/* transfer data size */
	SFC_SetDataSize(sfc->base, SPI_DATA_SIZE_8BIT);
	/* baudrate */
	SFC_SetBaudrate(sfc->base, sfc->freq);
	/* timing */
	SFC_SetTiming(sfc->base, 0);
	(void)SFC_DisableIrq(sfc->base, SPI_IRQ_ALL);
	(void)SFC_EnableIrq(sfc->base, SPI_IRQ_MSTIM);
	SFC_Enable(sfc->base, SPI_ENABLE);
	sfc_cs_activate(&sfc->slave);

	return 0;
}

void sfc_release_bus(struct spi_slave *slave)
{

}

int sfc_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
			void *din, unsigned long flags)
{
	return -ENOTSUPP;
}

struct spi_slave *sfc_setup_slave(unsigned int bus, unsigned int cs,
				unsigned int max_hz, unsigned int mode)
{
	sfc_controller *sfc;

	sfc = malloc(sizeof(sfc_controller));
	if (!sfc)
		return NULL;

	memset(sfc, 0, sizeof(sfc_controller));
	sfc->clk_in = SPI_SCLKIN;
	sfc->freq   = sfc->clk_in / max_hz;
	sfc->base   = SFC_REG_BASE;
	sfc->mode   = mode;
	sfc->sample_delay = 1;
	sfc->slave.bus    = bus;
	sfc->slave.cs     = cs;
	sfc->slave.mode   = SPI_RX_QUAD | SPI_RX_DUAL;
	sfc->slave.spi_xfer = sfc_xfer;
	sfc->slave.spi_claim_bus     = sfc_claim_bus;
	sfc->slave.spi_release_bus   = sfc_release_bus;
	sfc->slave.spi_free_slave    = sfc_free_slave;
	sfc->slave.spi_cs_activate   = sfc_cs_activate;
	sfc->slave.spi_cs_deactivate = sfc_cs_deactivate;
	
	sfc_cs_deactivate(&sfc->slave);

	return &sfc->slave;
}

static int get_if_type(enum spi_nor_protocol flash_read)
{
	enum fh_sfc_iftype if_type;
	int nbits = spi_nor_get_protocol_width(flash_read);

	switch (nbits) {
	case 2:
		if_type = IF_TYPE_DUAL;
		break;
	case 4:
		if_type = IF_TYPE_QUAD;
		break;
	default:
		if_type = IF_TYPE_STD;
		break;
	}

	return if_type;
}

static int fullhan_spi_nor_common_transfer(struct spi_nor *nor, loff_t start_off,
		void *dma_buf, size_t len, u8 op_type)
{
	struct spi_slave *slave = nor->spi;
	sfc_controller *sfc     = to_fh_sfc(slave);
	UINT32 base             = sfc->base;
	UINT32 address_length;

	SFC_Clear_Configure(base);
	SFC_SetCmdType(base, op_type);
	if (op_type == NFC_READ_CMD)
	{
		SFC_Send_CMD(base, nor->read_opcode);
		SFC_SetXferMode(base, get_if_type(nor->read_proto));
	}
	else
		SFC_Send_CMD(base, nor->program_opcode);

	if (op_type == NFC_READ_CMD)
		address_length = nor->addr_width + (nor->read_dummy >> 3);
	else
		address_length = nor->addr_width;

	SFC_SetDataType(base, DATA_ONLY);
	SFC_SetDataLength(base, len);
	SFC_SetAddressLength(base, address_length);
	SFC_SetAddress(base, start_off, nor->addr_width);
	SFC_Access_Start(base);
	SFC_Wait_OP_Finish(base);

	return 0;
}

void fullhan_spi_nor_set_rx_para(struct dma_transfer *trans, u32 src, u32 dst, u32 count, u32 data_width)
{
	trans->channel_number = 1;
	trans->fc_mode = DMA_P2M;
	trans->dst_add = dst;
	trans->src_add = src;
	trans->src_per = 33;
	trans->data_switch  = SWT_ABCD_ABCD;
	trans->src_inc_mode = DW_DMA_SLAVE_INC;
	trans->src_msize    = count * data_width;
	trans->dst_inc_mode = DW_DMA_SLAVE_INC;
	trans->dst_msize    = DW_DMA_SLAVE_MSIZE_8;
	trans->trans_len    = count;
	trans->ot_len_flag  = USR_DEFINE_ONE_TIME_LEN;

	if (data_width == 1) {
		trans->src_width = DW_DMA_SLAVE_WIDTH_8BIT;
		trans->dst_width = DW_DMA_SLAVE_WIDTH_8BIT;
	} else if (data_width == 2) {
		trans->src_width = DW_DMA_SLAVE_WIDTH_16BIT;
		trans->dst_width = DW_DMA_SLAVE_WIDTH_16BIT;
	} else {
		trans->src_width = DW_DMA_SLAVE_WIDTH_32BIT;
		trans->dst_width = DW_DMA_SLAVE_WIDTH_32BIT;
	}
}

static int fullhan_spi_nor_dma_transfer(struct spi_nor *nor, loff_t start_off,
		void *dma_buf, size_t len, u8 op_type)
{
	struct spi_slave *slave = nor->spi;
	sfc_controller *sfc     = to_fh_sfc(slave);
	UINT32 base             = sfc->base;
	UINT32 *src             = (UINT32 *)REG_SFC_PAGE_DATA_BUFFER(base);
	UINT32 data_width       = 1;
	UINT32 address_length;
	void *dma_rx_lli;
	struct dma_transfer rx_transfer = {0};

	SFC_Clear_Configure(base);
	SFC_SetCmdType(base, op_type);
	if (op_type == NFC_READ_CMD)
	{
		SFC_Send_CMD(base, nor->read_opcode);
		SFC_SetXferMode(base, get_if_type(nor->read_proto));
	}
	else
		SFC_Send_CMD(base, nor->program_opcode);

	if (op_type == NFC_READ_CMD)
		address_length = nor->addr_width + (nor->read_dummy >> 3);
	else
		address_length = nor->addr_width;

	if (((UINT32)dma_buf % 4 == 0) && (len % 4 == 0))
		data_width = 4;
	else if (((UINT32)dma_buf % 2 == 0) && (len % 2 == 0))
		data_width = 2;
	else
		data_width = 1;

	dma_rx_lli = get_dma_rx_lli_head();
	fullhan_spi_nor_set_rx_para(&rx_transfer, (UINT32)src, (UINT32)dma_buf, len / data_width, data_width);
	SFC_SetDataType(base, DATA_ONLY);
	SFC_SetDataLength(base, len);
	SFC_SetAddressLength(base, address_length);
	SFC_SetAddress(base, start_off, nor->addr_width);
	SFC_EnableDma(base);
	SFC_Access_Start(base);
	handle_single_transfer(&rx_transfer, dma_rx_lli);
	wait_dma_xfer_done(rx_transfer.channel_number);
	SFC_Wait_OP_Finish(base);

	return 0;
}


static int fullhan_spi_nor_op_reg(struct spi_nor *nor,
				u8 opcode, int len, u8 optype)
{
	struct spi_slave *slave = nor->spi;
	sfc_controller *sfc     = to_fh_sfc(slave);
	UINT32 base             = sfc->base;

	SFC_Clear_Configure(base);
	SFC_SetCmdType(base, optype);
	SFC_Send_CMD(base, opcode);
	if (len)
	{
		SFC_SetDataType(base, DATA_ONLY);
		SFC_SetDataLength(base, len);
	}
	SFC_Access_Start(base);
	SFC_Wait_OP_Finish(base);

	return 0;
}

int fullhan_spi_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
	struct spi_slave *slave = nor->spi;
	sfc_controller *sfc     = to_fh_sfc(slave);
	UINT32 base             = sfc->base;
	UINT32 *src             = (UINT32 *)REG_SFC_PAGE_DATA_BUFFER(base);
	int ret;

	ret = fullhan_spi_nor_op_reg(nor, opcode, len, NFC_READ_CMD);
	if (ret)
		return ret;
	memcpy(buf, src, len);

	return 0;
}

int fullhan_spi_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
	struct spi_slave *slave = nor->spi;
	sfc_controller *sfc     = to_fh_sfc(slave);
	UINT32 base             = sfc->base;
	UINT32 *dst             = (UINT32 *)REG_SFC_PAGE_DATA_BUFFER(base);

	memcpy(dst, buf, len);
	return fullhan_spi_nor_op_reg(nor, opcode, len, NFC_WRITE_CMD);
}

ssize_t fullhan_spi_nor_read(struct spi_nor *nor, loff_t from,
			size_t len, u_char *read_buf)
{
	struct spi_slave *slave = nor->spi;
	sfc_controller *sfc     = to_fh_sfc(slave);
	UINT32 base             = sfc->base;
	UINT32 *src             = (UINT32 *)REG_SFC_PAGE_DATA_BUFFER(base);
	size_t offset           = 0;
	int ret;

#ifdef CONFIG_FH_MC_DMA
	while (len != offset) {
		size_t trans = min_t(size_t, SFC_DMA_MAX_LEN, len - offset);
		ret = fullhan_spi_nor_dma_transfer(nor, from, read_buf + offset, trans, NFC_READ_CMD);
		if (ret) {
			dev_warn(nor->dev, "read timeout\n");
			return ret;
		}
		from   += trans;
		offset += trans;
	} 
#else
	for (offset = 0; offset < len; offset += SFC_DMA_MAX_LEN) {
		size_t trans = min_t(size_t, SFC_DMA_MAX_LEN, len - offset);
		ret = fullhan_spi_nor_common_transfer(nor,
			from + offset, NULL, trans, NFC_READ_CMD);
		if (ret) {
			dev_warn(nor->dev, "read timeout\n");
			return ret;
		}
		memcpy(read_buf + offset, src, trans);
	}
#endif

	return len;
}

ssize_t fullhan_spi_nor_write(struct spi_nor *nor, loff_t to,
			size_t len, const u_char *write_buf)
{
	struct spi_slave *slave = nor->spi;
	sfc_controller *sfc     = to_fh_sfc(slave);
	UINT32 base             = sfc->base;
	UINT32 *dst             = (UINT32 *)REG_SFC_PAGE_DATA_BUFFER(base);
	size_t offset;
	int ret;

	for (offset = 0; offset < len; offset += SFC_DMA_MAX_LEN) {
		size_t trans = min_t(size_t, SFC_DMA_MAX_LEN, len - offset);
		memcpy(dst, write_buf + offset, trans);
		ret = fullhan_spi_nor_common_transfer(nor,
			to + offset, NULL, trans, NFC_WRITE_CMD);
		if (ret) {
			dev_warn(nor->dev, "write timeout\n");
			return ret;
		}
	}

	return len;
}

int spi_nor_register(struct spi_nor *nor)
{
	nor->read      = fullhan_spi_nor_read;
	nor->write     = fullhan_spi_nor_write;
	nor->read_reg  = fullhan_spi_nor_read_reg;
	nor->write_reg = fullhan_spi_nor_write_reg;
	nor->erase     = NULL;

	return 0;
}
